package com.autonavi.yunda.yunji.core.code.generator

import com.autonavi.yunda.yunji.common.utils.JsonUtils
import com.autonavi.yunda.yunji.core.config.EngineConstant
import com.autonavi.yunda.yunji.core.engine.scriptresolver.CodeVisitor
import com.autonavi.yunda.yunji.core.enums.ParamType
import com.autonavi.yunda.yunji.core.utils.DynamicConfigUtil
import com.autonavi.yunda.yunji.core.vo.param.NodeParam
import groovy.util.logging.Slf4j
import org.apache.commons.lang3.StringUtils

import static com.autonavi.yunda.yunji.core.config.EngineConstant.DYNAMIC_CONFIG_REF

/**
 * @author cizhong.wcs
 * @date 2024/5/24 10:47
 */
@Slf4j
class GroovyCodeGenerator {
    private static final String NEW_LINE = "\n";
    private static final String TAB = "\t";
    private final String subPackage;

    private final String name;

    private final String script;

    private final List<NodeParam> params;

    public static final Set<String> DEFAULT_IMPORTS = Set.of(
            "import org.slf4j.LoggerFactory",
            "import org.slf4j.Logger",
            "import com.fasterxml.jackson.databind.ObjectMapper"
    );


    private final String projectId;

    GroovyCodeGenerator(String name,
                        String script,
                        List<NodeParam> params,
                        String subPackage,
                        String projectId) {
        this.name = name;
        this.script = script;
        this.params = params;
        this.subPackage = subPackage;
        this.projectId = projectId;
    }


    static String capitalize(String str) {
        if (StringUtils.isEmpty(str)) {
            return str;
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }

    private String groovyCode(CodeVisitor visitor) {
        StringBuilder code = new StringBuilder();
        addPackage(code, subPackage);
        addImports(code, visitor);
        addClassDeclare(code, name);
        addLogger(code);
        addProcessFunction(code, visitor);
        addDefaultMainFunction(name, code);
        addDefaultLogDebugFunction(code);
        addProperties(code, params);
        addClassFinish(code);
        return code.toString();
    }

    String toGroovyContent() throws Exception {
        if (StringUtils.isEmpty(name)) {
            throw new Exception("node name should not empty");
        }
        if (StringUtils.isEmpty(script)) {
            return groovyCode(CodeVisitor.EMPTY_VISITOR);
        }
        try {
            return groovyCode(CodeVisitor.toCodeVisitor(script));
        } catch (Exception e) {
            log.error("toGroovyContent error ", e);
            return groovyCode(CodeVisitor.EMPTY_VISITOR);
        }

    }


    private void addImports(StringBuilder code, CodeVisitor visitor) {
        mergeSets(visitor.allImports, DEFAULT_IMPORTS).forEach(ip -> code.append(ip).append(NEW_LINE));
        code.append(NEW_LINE);
    }

    private void addLogger(StringBuilder code) {
        code.append("\tLogger log = LoggerFactory.getLogger(\"").append(name).append("\");").append(NEW_LINE);
        code.append(NEW_LINE);
    }

    @SafeVarargs
    private Set<String> mergeSets(Set<String>... sets) {
        Set<String> merge = new TreeSet<>();
        Arrays.stream(sets).forEach(merge::addAll);
        return merge;
    }

    private void addClassDeclare(StringBuilder code, String nodeName) {
        code.append("class ").append(capitalize(nodeName)).append("{").append(NEW_LINE);
        code.append(NEW_LINE);
    }

    private void addProperties(StringBuilder code, List<NodeParam> params) {
        if (params == null) {
            return;
        }
        for (NodeParam param : params) {
            Object debugVal = param.debugValue;
            if (DYNAMIC_CONFIG_REF.equals(param.dataSource) && projectId != null) {
                debugVal = DynamicConfigUtil.getString(projectId, param.getDebugValue() + "");
            }
            code.append(TAB).append("def ").append(param.getParamNameInScript()).append(" = ");
            if (debugVal != null) {
                if (param.type.equals(ParamType.OBJECT.getCode()) || param.type.equals(ParamType.ARRAY.getCode())) {
                    String val = debugVal + "";
                    code.append(toGroovyLiteral(JsonUtils.from(val, Object.class)))
                } else if (param.type.equals(ParamType.STRING.getCode())) {
                    String val = debugVal + "";
                    if (!"".equals(val)) {
                        code.append("\"\"\"").append(debugVal).append("\"\"\"");
                    }
                } else if (param.type.equals(ParamType.BOOLEAN.getCode())) {
                    if ("true".equals(debugVal)) {
                        code.append("true");
                    } else if ("false".equals(debugVal)) {
                        code.append("false");
                    }
                } else {//number
                    if (!"".equals(debugVal)) {
                        code.append(debugVal);
                    }
                }
            } else {
                code.append("null");
            }
            code.append(NEW_LINE);
        }
        code.append(NEW_LINE);
    }

    static String toGroovyLiteral(def obj) {
        if (obj instanceof Map) {
            if (obj.isEmpty()) {
                return "[:]"
            }
            return obj.collect { k, v -> "\"${k}\" : ${toGroovyLiteral(v)}" }.join(", ").with { "[ $it ]" }
        } else if (obj instanceof Collection) {
            return obj.collect { toGroovyLiteral(it) }.join(", ").with { "[ $it ]" }
        } else if (obj instanceof String) {
            return "\"\"\"${obj}\"\"\""
        } else {
            return obj.toString()
        }
    }

    private void addPackage(StringBuilder code, String subPath) {
        code.append("package ").append(subPath).append(NEW_LINE);
        code.append(NEW_LINE);
    }

    private void addProcessFunction(StringBuilder code, CodeVisitor visitor) {
        String content = visitor.getMethods().get("process");
        if (content == null) {
            addDefaultProcessFunction(code);
        } else {
            code.append(content);
        }
        code.append(NEW_LINE);
    }

    private void addDefaultProcessFunction(StringBuilder code) {
        code.append(TAB).append("def process(){").append(NEW_LINE).append(TAB).append(TAB).append(NEW_LINE).append(TAB).append("}").append(NEW_LINE).append(NEW_LINE);
    }

    private void addDefaultMainFunction(String name, StringBuilder code) {
        code.append(TAB).append("static void main(String[] args) {").append(NEW_LINE);
        code.append(TAB).append(TAB).append("println(new ObjectMapper().writeValueAsString(new ").append(capitalize(name)).append("().process()))").append(NEW_LINE);
        code.append(TAB).append("}").append(NEW_LINE);
        code.append(NEW_LINE);
    }

    private void addDefaultLogDebugFunction(StringBuilder code) {
        code.append("    /**\n" +
                "     * 单节点调试时用于debug打印日志,该方法调用的内容会在日志信息中展示\n" +
                "     * 正式链路中方法体为空\n" +
                "     *\n" +
                "     * @param __debug_info 日志内容\n" +
                "     */").append(NEW_LINE);
        code.append(TAB).append("def ").append(EngineConstant.EXEC_LOG_DEBUG_INFO_PROCESS).append("(__debug_info) {}").append(NEW_LINE);
        code.append(NEW_LINE);

    }

    private void addClassFinish(StringBuilder code) {
        code.append(NEW_LINE).append("}").append(NEW_LINE);
    }
}
